package com.example.springbootoauth2jwtclientserver.config;

import com.alibaba.fastjson.JSONObject;
import com.example.springbootoauth2jwtclientserver.exception.CustomException;
import com.example.springbootoauth2jwtclientserver.security.CustomAccessDeniedHandler;
import lombok.experimental.WithBy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.event.LoggerListener;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.JdbcOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Configuration
@EnableWebSecurity
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Value("${oauth2.server.url}")
    private String oauth2ServerLogout;

    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;
    @Autowired
    private OAuth2AuthorizedClientService oAuth2AuthorizedClientService;

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/webjars/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.logout().logoutSuccessUrl(oauth2ServerLogout);
        http.authorizeRequests()
                .mvcMatchers("/oauth/login").permitAll()
                .antMatchers("/").authenticated()
                .antMatchers("/user/**","/user2/**").hasAuthority("SCOPE_any")
                .anyRequest().access("@rbacService.hasPermission(request,authentication)");
        http.oauth2Login().loginPage("/oauth/login");
        http.oauth2Client().authorizedClientService(oAuth2AuthorizedClientService);
        http.csrf().disable();
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);

        /*http.oauth2ResourceServer().jwt()
                .jwtAuthenticationConverter(source -> {
                    Set<SimpleGrantedAuthority> authorities = ((Collection<String>)source.getClaims().get("authorities")).stream()
                            .map(SimpleGrantedAuthority::new).collect(Collectors.toSet());
                    Set<SimpleGrantedAuthority> scopes = ((Collection<String>)source.getClaims().get("scope")).stream()
                            .map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope)).collect(Collectors.toSet());
                    authorities.addAll(scopes);
                    return new JwtAuthenticationToken(source,authorities);
                });*/

    }

    /**
     * 认证事件监听器,打印日志
     * @return
     */
    @Bean
    public LoggerListener loggerListener(){
        return new LoggerListener();
    }

    /**
     * 访问事件监听器，打印日志
     * @return
     */
    @Bean
    public org.springframework.security.access.event.LoggerListener eventLoggerListener(){
        return new org.springframework.security.access.event.LoggerListener();
    }

    @Bean
    public OAuth2AuthorizedClientService authorizedClientService(JdbcOperations jdbcOperations, ClientRegistrationRepository clientRegistrationRepository){
        JdbcOAuth2AuthorizedClientService authorizedClientService = new JdbcOAuth2AuthorizedClientService(jdbcOperations, clientRegistrationRepository);
        return authorizedClientService;
    }

    @Bean
    public RestTemplateCustomizer restTemplateCustomizer(OAuth2AuthorizedClientService clientService){
        return new RestTemplateCustomizer() {
            @Override
            public void customize(RestTemplate restTemplate) {
                List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
                if (CollectionUtils.isEmpty(interceptors)) {
                    interceptors = new ArrayList<>();
                }
                interceptors.add(new ClientHttpRequestInterceptor() {
                    @Override
                    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
                        OAuth2AuthenticationToken auth = (OAuth2AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
                        String clientRegistrationId = auth.getAuthorizedClientRegistrationId();
                        String principalName = auth.getName();
                        OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(clientRegistrationId,principalName);
                        if (client == null) {
                            throw new CustomException(HttpStatus.NOT_ACCEPTABLE,"用户状态异常，请重新登录");
                        }
                        String accessToken = client.getAccessToken().getTokenValue();
                        request.getHeaders().add("authorization","Bearer " + accessToken);
                        log.info("请求地址：{}",request.getURI());
                        log.info("请求头信息：{}", JSONObject.toJSONString(request.getHeaders(),true));
                        ClientHttpResponse response = execution.execute(request, body);
                        log.info("相应头信息：{}",JSONObject.toJSONString(response.getHeaders(),true));
                        return response;
                    }
                });
            }
        };
    }
}
